package com.pep.mgmt.service;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
import java.util.Optional;

import org.springframework.stereotype.Service;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.pep.mgmt.model.entity.ConfigEntity;
import com.pep.mgmt.model.entity.RcstIpTablesRule;
import com.pep.mgmt.model.entity.RunningEntity;
import com.pep.mgmt.model.entity.VrfEntity;
import com.pep.mgmt.repo.ConfigRepo;
import com.pep.mgmt.repo.RcstIpTableRuleRepo;
import com.pep.mgmt.repo.RunningProgramRepo;
import com.pep.mgmt.repo.VrfMgmtRepo;
import com.pep.mgmt.util.CONST;

import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
public class PepMgmtService {

    private final VrfMgmtRepo vrfMgmtRepo;
    private final RunningProgramRepo runningProgramRepo;
    private final RcstIpTableRuleRepo rcstIpTableRuleRepo;
    private final ConfigRepo configRepo;

    public PepMgmtService(VrfMgmtRepo vrfMgmtRepo, RunningProgramRepo runningProgramRepo, RcstIpTableRuleRepo rcstIpTableRuleRepo, ConfigRepo configRepo) {
        this.vrfMgmtRepo = vrfMgmtRepo;
        this.runningProgramRepo = runningProgramRepo;
        this.rcstIpTableRuleRepo = rcstIpTableRuleRepo;
        this.configRepo = configRepo;
    }

    public void createVlan(VrfEntity vrfEntity) {
        vrfMgmtRepo.save(vrfEntity);
    }

    public List<VrfEntity> findAllVrfs() {
        return vrfMgmtRepo.findAll();
    }

    public List<VrfEntity> findVrfByName(String vrfName) {
        return vrfMgmtRepo.findByVrfName(vrfName);
    }

    public void deleteByVrfName(String vrfName) {
        vrfMgmtRepo.deleteAllByVrfName(vrfName);
    }

    public List<RunningEntity> findAllRunningPrograms() {
        return runningProgramRepo.findAll();
    }

    public void addOneRunningProgram(RunningEntity runningEntity) {
        runningProgramRepo.save(runningEntity);
    }

    public void removeRunningProgram(String vrfName) {
        runningProgramRepo.deleteAllByVrfName(vrfName);
    }

    public void removeAllRunningPrograms() {
        runningProgramRepo.deleteAll();
    }

    public void markRunningProgramStopped(String vrfName) {
        List<RunningEntity> runningEntities = runningProgramRepo.findAllByVrfName(vrfName);
        for (RunningEntity re : runningEntities) {
            re.setRequiredStopped(true);
            runningProgramRepo.save(re);
        }
    }

    public List<RunningEntity> findRunningProgramByVrfName(String vrfName) {
        return runningProgramRepo.findAllByVrfName(vrfName);
    }

    public void updateRunningProgramPid(RunningEntity re, long pId) {
        re.setPId(pId);
        runningProgramRepo.save(re);
    }

    public void addRcstIpTableRule(RcstIpTablesRule rcstIpTableRule) {
        rcstIpTableRuleRepo.save(rcstIpTableRule);
    }

    public List<RcstIpTablesRule> getAllRcstIpTableRules() {
        return rcstIpTableRuleRepo.findAll();
    }

    public Optional<RcstIpTablesRule> findRcstIpTableRulesByVrfNameAndRcstIpAddr(String vrfName, String rcstIpAddress) {
        return rcstIpTableRuleRepo.findByVrfNameAndRcstIpAddr(vrfName, rcstIpAddress);
    }

    public void deleteRcstIpTableRuleByVrfNameAndRcstIpAddr(String vrfName, String rectIpAddress) {
        rcstIpTableRuleRepo.deleteAllByVrfNameAndRcstIpAddr(vrfName, rectIpAddress);
    }

    public long vrfCountByVrfNameAndVlanId(String vrfName, int vlanId) {
        return vrfMgmtRepo.countByVrfNameAndVlanId(vrfName, vlanId);
    }

    public long runningProgramCountByVrfName(String vrfName) {
        return runningProgramRepo.countByVrfName(vrfName);
    }

    public long rcstIpTablesRuleCountByVrfNameAndRcstIpAddr(String vrfName, String rcstIpAddr) {
        return rcstIpTableRuleRepo.countByVrfNameAndRcstIpAddr(vrfName, rcstIpAddr);
    }

    public List<ConfigEntity> getConfig() {
        return configRepo.findAll();
    }
    
    public void clearPendingConfigUpdate() {
        List<ConfigEntity> configEntities = configRepo.findAll();
        for (ConfigEntity ce : configEntities) {
            ce.setPendingConfigUpdate(false);
            configRepo.save(ce);
        }
    }

    public void updateConfig(ConfigEntity configEntity, boolean initialFlag) {

        List<ConfigEntity> configEntities = configRepo.findAll();

        if (initialFlag) {
            if (configEntities.isEmpty()) {
                configRepo.save(configEntity);
                saveConfigToFile(CONST.CONFIG_FILE, CONST.CONFIG_FILE_PATH, configEntity);
            } else {
                for (ConfigEntity ce : configEntities) {
                    saveConfigToFile(CONST.CONFIG_FILE, CONST.CONFIG_FILE_PATH, ce);
                }
            }
        } else {
            if(configEntities.size() > 1) {
                log.error("duplciated configurations!");
            } else {
                for (ConfigEntity ce : configEntities) {
                    ce.setClientTcpAlgo(configEntity.getClientTcpAlgo());
                    ce.setClientTcpHighWaterMark(configEntity.getClientTcpHighWaterMark());
                    ce.setClientTcpRecvBuffer(configEntity.getClientTcpRecvBuffer());
                    ce.setPendingConfigUpdate(true);
                    ce.setTunnelTcpAlgo(configEntity.getTunnelTcpAlgo());
                    ce.setTunnelTcpHighWaterMark(configEntity.getTunnelTcpHighWaterMark());
                    ce.setTunnelTcpLowWaterMark(configEntity.getTunnelTcpLowWaterMark());
                    configRepo.save(ce);
                    saveConfigToFile(CONST.CONFIG_FILE, CONST.CONFIG_FILE_PATH, ce);
                }
            }
        }
    }

    private String toJson(Object object) {

        ObjectMapper mapper = new ObjectMapper();
        String json = null;
        try {
            json = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);
            log.debug("to json succssfully {}", json);
        } catch (JsonProcessingException ex) {
            log.warn("to json error {}", ex.getMessage());
        }
        log.info("config json string {}", json);
        return json;
    }

    public boolean saveConfigToFile(String fileName, String filePath, ConfigEntity ce) {

        File configDir = new File(filePath);
        if (!configDir.exists()) {
            boolean result = configDir.mkdir();
            if (result) {
                log.info("create dir {} successfully", filePath);
            } else {
                log.warn("failed to create dir {}", filePath);
                return false;
            }
        }

        try(FileWriter fout1 = new FileWriter(filePath + fileName);
            BufferedWriter fout2 = new BufferedWriter(fout1)
            ) {
            fout2.write(toJson(ce));
        } catch (IOException e) {
            log.warn(e.getMessage());
            return false;
        }
        return true;
    }

    public boolean saveConfigToFile(String fileName, String filePath, String content) {

        try(FileWriter fout1 = new FileWriter(filePath + fileName);
            BufferedWriter fout2 = new BufferedWriter(fout1)
            ) {
            fout2.write(content);
        } catch (IOException e) {
            log.warn(e.getMessage());
            return false;
        }
        return true;
    }
}
